﻿using Core.Entities;
using Core.Enums;
using Core.Reader;
using Core.ReadWriter;
using Core.Tools;
using Core.Writer;
using System;
using System.Collections.Generic;

namespace Core.Reader
{
    public class CustomizableReader : IDataReader, IDataWriter
    {
        private static readonly object NullValue = new object();

        public class SortItem
        {
            internal readonly CustomizableReader Content;
            internal int oldIndex;
            internal int newIndex;
            internal string oldName;
            internal string newName;

            internal SortItem(CustomizableReader Content)
            {
                this.Content = Content;
            }

            public string OldName
            {
                get
                {
                    return oldName;
                }
            }

            public string NewName
            {
                get
                {
                    return newName;
                }
            }

            public int OldIndex
            {
                get
                {
                    return oldIndex;
                }
            }

            public int NewIndex
            {
                get
                {
                    return newIndex;
                }
            }

            public object Value
            {
                get
                {
                    return Content.Reader[oldIndex];
                }
            }
        }

        private class INItem
        {
            public int NewIndex;
            public int OldIndex;
            public string OldName;

            public INItem(int Index, string Name)
            {
                OldIndex = Index;
                NewIndex = Index;

                OldName = Name;
            }
        }
        
        private IDataReader Reader;
        private IDataWriter Writer;
        private ParamsDictionary<string, INItem> INMap;
        private List<IReaderGetter> Getters;
        private List<IReaderGetter> Setters;

        private CustomizableReader()
        {
            Getters = new List<IReaderGetter>();
            Setters = new List<IReaderGetter>();
        }

        public CustomizableReader(IDataReader Reader) : this()
        {
            this.Reader = Reader;

            Writer = Reader as IDataWriter;

            ReaderInit();
        }

        public CustomizableReader(BaseReadWriter ReadWriter) : this((IDataReader)ReadWriter)
        {

        }

        private void ReaderInit()
        {
            INMap = new ParamsDictionary<string, INItem>();

            foreach (var Item in Reader.Fields)
            {
                INMap.Add(Item, new INItem(INMap.Count, Item));
            }
        }

        private void CheckName(ref string Name)
        {
            INItem iNItem;

            if (INMap.TryGetValue(Name, out iNItem))
            {
                Name = iNItem.OldName;
            }
            else
            {
                throw new KeyNotFoundException(Name);
            }
        }

        private void CheckIndex(ref int Index)
        {
            if (Index >= 0 && Index < Count)
            {
                Index = INMap[Index].Value.OldIndex;
            }
            else
            {
                throw new IndexOutOfRangeException("Index : " + Index);
            }
        }

        public void AddGetter(IReaderGetter Getter)
        {
            if (Getter == null)
            {
                throw new NullReferenceException("Getter");
            }

            Getters.Add(Getter);
        }

        public void AddGetter(AsGetValueDelegate Getter)
        {
            if (Getter == null)
            {
                throw new NullReferenceException("Getter");
            }

            AddGetter(new DelegateReaderGetter(Getter));
        }

        public void AddSetter(IReaderGetter Setter)
        {
            if (Setter == null)
            {
                throw new NullReferenceException("Setter");
            }

            Setters.Add(Setter);
        }

        public void AddSetter(AsGetValueDelegate Setter)
        {
            if (Setter == null)
            {
                throw new NullReferenceException("Setter");
            }

            AddSetter(new DelegateReaderGetter(Setter));
        }

        public void RemoveField(string Name)
        {
            int Index = INMap.FindLastIndex(Name);

            INMap.RemoveLast(Name);

            foreach (var Item in INMap)
            {
                if (Item.Value.NewIndex > Index)
                {
                    --Item.Value.NewIndex;
                }
            }
        }

        public void RemoveField(int Index)
        {
            INMap.RemoveLast(INMap[Index].Key);

            foreach (var Item in INMap)
            {
                if (Item.Value.NewIndex > Index)
                {
                    --Item.Value.NewIndex;
                }
            }
        }

        public void Rename(string OldName, string NewName)
        {
            var INItem = INMap[OldName];

            INMap.RemoveLast(OldName);

            INMap.Add(NewName, INItem);
        }

        public void Rename(int Index, string NewName)
        {
            var INItem = INMap[Index];

            INMap.RemoveLast(INItem.Key);

            INMap.Add(NewName, INItem.Value);
        }

        public void SortFields(IComparer<SortItem> comparer)
        {
            SortItem xItem = new SortItem(this);
            SortItem yItem = new SortItem(this);

            INMap.Sort((x, y) =>
            {
                xItem.oldIndex = x.Value.OldIndex;
                xItem.newIndex = x.Value.NewIndex;
                xItem.newName = x.Key;
                xItem.oldName = x.Value.OldName;


                yItem.oldIndex = y.Value.OldIndex;
                yItem.newIndex = y.Value.NewIndex;
                yItem.newName = y.Key;
                yItem.oldName = y.Value.OldName;

                return comparer.Compare(xItem, yItem);
            });

            int Index = 0;

            foreach (var Item in INMap)
            {
                Item.Value.NewIndex = Index;

                ++Index;
            }
        }

        public void SortFields(Comparison<SortItem> comparer)
        {
            SortItem xItem = new SortItem(this);
            SortItem yItem = new SortItem(this);

            INMap.Sort((x, y) =>
            {
                xItem.oldIndex = x.Value.OldIndex;
                xItem.newIndex = x.Value.NewIndex;
                xItem.newName = x.Key;
                xItem.oldName = x.Value.OldName;


                yItem.oldIndex = y.Value.OldIndex;
                yItem.newIndex = y.Value.NewIndex;
                yItem.newName = y.Key;
                yItem.oldName = y.Value.OldName;

                return comparer(xItem, yItem);
            });

            int Index = 0;

            foreach (var Item in INMap)
            {
                Item.Value.NewIndex = Index;

                ++Index;
            }
        }

        public object this[string Name]
        {
            get
            {
                CheckName(ref Name);

                object Value = Reader[Name];

                for (int i = Getters.Count - 1; i >= 0; --i)
                {
                    if (Getters[i].AsGetValue(ref Value, Name, -1))
                    {
                        break;
                    }
                }

                return Value;
            }
            set
            {
                CheckName(ref Name);

                object Value = value;

                for (int i = Setters.Count - 1; i >= 0; --i)
                {
                    if (Setters[i].AsGetValue(ref Value, Name, -1))
                    {
                        break;
                    }
                }

                Writer[Name] = Value;
            }
        }

        public object this[int Index]
        {
            get
            {
                CheckIndex(ref Index);

                object Value = Reader[Index];

                for (int i = Getters.Count - 1; i >= 0; --i)
                {
                    if (Getters[i].AsGetValue(ref Value, null, Index))
                    {
                        break;
                    }
                }

                return Value;
            }
            set
            {
                CheckIndex(ref Index);

                object Value = value;

                for (int i = Setters.Count - 1; i >= 0; --i)
                {
                    if (Setters[i].AsGetValue(ref Value, null, Index))
                    {
                        break;
                    }
                }

                Writer[Index] = Value;
            }
        }

        public IEnumerable<string> Fields
        {
            get
            {
                return INMap.Keys;
            }
        }

        public int Count
        {
            get
            {
                return INMap.Count;
            }
        }

        public RWTypes ObjectType
        {
            get
            {
                return Reader.ObjectType;
            }
            set
            {
                Writer.ObjectType = value;
            }
        }

        public void Add(string Name, object Value)
        {
            CheckName(ref Name);

            for (int i = Setters.Count - 1; i >= 0; --i)
            {
                if (Setters[i].AsGetValue(ref Value, Name, -1))
                {
                    break;
                }
            }

            Writer.Add(Name, Value);
        }

        public IDataWriter CreateChildrenWriter(CreateChildrenWriterInfo Info)
        {
            Info = TypeHelper.Clone(Info);

            CheckName(ref Info.Name);

            if (Info.Index != null)
            {
                int Index = Info.Index.Value;

                CheckIndex(ref Index);

                Info.Index = Index;
            }

            return Writer.CreateChildrenWriter(Info);
        }

        public void Dispose()
        {
            if (Reader != null)
            {
                Reader.Dispose();
            }
            else if (Writer != null)
            {
                Writer.Dispose();
            }
        }

        public void Next()
        {
            Writer.Next();
        }

        public bool Read()
        {
            return Reader.Read();
        }

        public bool TryGetValue(string Name, out object Value)
        {
            CheckName(ref Name);

            if (Reader.TryGetValue(Name, out Value))
            {
                for (int i = Getters.Count - 1; i >= 0; --i)
                {
                    if (Getters[i].AsGetValue(ref Value, Name, -1))
                    {
                        break;
                    }
                }
            }

            return false;
        }
    }
}